package legato.injection
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.core.Application;
	import mx.core.IMXMLObject;
	import mx.events.*;
	

	/**
	* Dispatched before on initialization start, before components are registered.
	*/
	[Event(name="injectionInitStart", type="legato.injection.InjectionContainerEvent")]
	/**
	* Dispatched on initialization end, after all components are registered and container is registered in <code>DILocator</code>
	*/
	[Event(name="injectionInitComplete", type = "legato.injection.InjectionContainerEvent")]
	
	/**
	 * Basic class for injection configuraion, implements depedency injection pattern. <br/>
	 * Can be used also as a service locator.
	 * 
	 * Simple example of configuration:<br/>
	 * <code><pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
	&lt;InjectionContainer xmlns="legato.injection.*" 
			xmlns:mx="http://www.adobe.com/2006/mxml" 
			xmlns:actions="legato.actions.*"&gt;
		
		&lt;DIComponent id="component1" scope="application" componentClass="{ExplicitCommandImpl}" 
			 &gt;
			&lt;constructorParams&gt;
				&lt;mx:Array&gt;
					&lt;mx:String&gt;string parameter&lt;/mx:String&gt;
					&lt;mx:Number&gt;2&lt;/mx:Number&gt;
					&lt;mx:Object&gt;{component2}&lt;/mx:Object&gt;
				&lt;/mx:Array&gt;
			&lt;/constructorParams&gt;
		&lt;/DIComponent&gt;
		
		&lt;DIComponent id="component2" scope="application" componentClass="{SimpleComponent}" /&gt;
		
		&lt;actions:AbstractAction id="action1" name="addAction" /&gt;
		&lt;actions:AbstractAction id="action2" name="copyAction"/&gt;
	
	&lt;/InjectionContainer&gt;
	 * </pre></code>
	 * Every child object is registered as a component with it's <code>id</code> as a key.
	 * Actually as <code>InjectionContainer</code> uses reflection, every public and bindable 
	 * property is registered.<br/><br/>
	 * <b>Using DIComponent </b><br/>
	 * DIComponent is basig object for creating components. Basic attributes are:
	 * <ol>
	 * <li><code>id</code> - must be specified to identify component; used as a key;</li>
	 * <li><code>scope</code> - scope of component; three values are allowed:<br/>
	 * <ul>
		 * <li><i>application</i> : only one component instance exists for whole application,</li>
		 * <li><i>context</i> : there is one component instance for every context, </li>
		 * <li><i>instance</i> : new instance is created every time component is requested. </li>
	 * </ul>
	 * Default value is <i>application</i></li>
	 * <li><code>componentClass</code> - a class of component. Remember to use Class object, not just class name. 
	 * As in the example <code>componentClass="{SimpleComponent}"</code> .
	 * </li>
	 * <li><code>setterTemplate</code> - object that contains params to inject in created component instance</li>
	 * <li><code>constructorParams</code> - array of construcotor parameters</li>
	 * </ol>
	 * Any other attribute of <code>DIComponent</code> tag will be used for setter injection. 
	 * These parameters overrides <code>setterTemplate</code> object properties.<br/>
	 * Example:<br/>
	 * <code><pre>
	 * &lt;DIComponent id="component2" scope="application" componentClass="{SimpleComponent}" /&gt;
	 * &lt;DIComponent id="component3" scope="application" componentClass="{SimpleComponent2}" param1="param1Val" param2="99" /&gt;
	 * </pre></code>
	 * <code>setterTemplate</code> is used to pass object, that contains params to inject in created component instance. <br/>
	 * Example of usage:
	 * <code><pre>
	&lt;mx:Script&gt;
		&lt;![CDATA[
			private var setterTemplate3:Object = 
			{
				param1:"param1Value",
				param2:99
			}
		]]&gt;
	&lt;/mx:Script&gt;
	
	&lt;DIComponent id="component3" scope="application" componentClass="{SimpleComponent2}" setterTemplate="{setterTemplate3}" /&gt;
	 * </pre></code>
	 * The example above does the same, that declaration in the previous example:<br/>
	 * <code>
	 * &lt;DIComponent id="component3" scope="application" componentClass="{SimpleComponent2}" param1="param1Val" param2="99" /&gt;
	 * </code><br/>
	 * which is simpler and preferred method.<br/> In these examples <code>SimpleComponent2</code> can be:<br/>
	 * <code><pre>
package poc
{
	public class SimpleComponent2
	{
		public var param1:String;
		
		private var _param2:Number;
		
		public function set param2(val:Number):void
		{
			this._param2 = val;
		}
		
		public function get param2():Number
		{
			return this.param2;
		}
		
	}
}</pre></code>
 	 * <br/>
 	 * <b>Constructor injection</b><br />
 	 * You can use <code>constructorParams</code> property to pass array of parameters to constructor. 
 	 * You can pass up to 15 parameters. Example of usage : <br/>
	 * <code><pre>
	&lt;DIComponent id="component1" scope="application" componentClass="{ExplicitCommandImpl}" 
		 &gt;
		&lt;constructorParams&gt;
			&lt;mx:Array&gt;
				&lt;mx:String&gt;string parameter&lt;/mx:String&gt;
				&lt;mx:Number&gt;2&lt;/mx:Number&gt;
				&lt;mx:Object&gt;{component2}&lt;/mx:Object&gt;
			&lt;/mx:Array&gt;
		&lt;/constructorParams&gt;
	&lt;/DIComponent&gt;
	 * </pre></code>
	 * <b>Simple objects</b><br/>Example:<br/>
	 * <code>&lt;actions:AbstractAction id="action1" name="addAction" /&gt;</code><br />
	 * Objects like this are registered with <code>WrapperComponent</code> . The scope of the component is always 
	 * <i>application</i>.<br/><br/>
	 * 
	 * <b>Injection and binding</b><br/>
	 * To perform injection use Flex binding. You can put binded component into dynamic parameters, setter template or constructor parameters.
	 * Injected object will be resolved during creation of requested component according to its <code>scope</code> attribute.
	 * In case of <i>context</i> scope, the scope name is propagated from parent component request.
	 *  
	 * @author Piotr
	 * @see DIComponent
	 * @see WrapperComponent
	 * 
	 */
	public class InjectionContainer extends EventDispatcher implements IMXMLObject
	{
		
		private var _components:Array;
		
		[ArrayElementType("legato.injection.IDIComponent")]
		private var registeredComponents:Array;
		
		
		public function InjectionContainer()
		{
			super();
			this.registeredComponents = new Array();
		}
		
		
		/**
		 * Registers new component in container. 
		 * @param key name to identify the component in container; must be unique
		 * @param scope component scope; can be : "application", "context" or "instance"
		 * @param componentClass the class of component
		 * @param setterTemplate setter template object
		 * @param constructorParams array of constructor parameters 
		 * 
		 */
		public function registerDIComponent(key:String, scope:String,componentClass:Class, setterTemplate:Object, constructorParams:Array = null):void 
		{
			if (!this.registeredComponents[key]) 
			{
				var diComponent:DIComponent = DIComponent.create(scope,componentClass,setterTemplate, constructorParams);
				this.registeredComponents[key] = diComponent;
			}
			else
			{
				throw new Error("Wrong key : "+key+" DIComponent key must be uniqe");
			}
		}
		
		/**
		 * Registers simple component. Creates new WrapperComponent instace. Scope is always <i>application</i>. 
		 * @param key name to identify the component in container; must be unique
		 * @param component component object to register
		 * 
		 */
		public function registerComponent(key:String, component:Object):void
		{
			if (!this.registeredComponents[key]) 
			{
				var newWrapperComponent:WrapperComponent = WrapperComponent.create(component);
				this.registeredComponents[key] = newWrapperComponent;
			} 
			else 
			{
				throw new Error("Wrong key : "+key+" DIComponent key must be uniqe");
			}
		}


		/**
		 * From IMXMLObject
		 * @param document
		 * @param key
		 */
		public function initialized(document:Object, key:String):void
		{
			this.dispatchEvent(new InjectionContainerEvent(InjectionContainerEvent.INJECTION_INIT_START));
			var description:XML = describeType(this);
			//Wszystkie propertiesy zadeklarowane w tej klasie a nie w bazowych, o dostępie readwrite i Bindable
			var items:XMLList = description.accessor.
								(@declaredBy==getQualifiedClassName(this) 
								&& @access=="readwrite"
								&& metadata.@name=="Bindable");
			
			for(var i:String in items)
			{
				var key:String = items[i].@name;
				var sourceComponent:Object = this[key];
				
				if (!this.registeredComponents[key]) 
				{
					if (sourceComponent is DIComponent)
					{
						var sourceDIComponent:DIComponent = DIComponent(sourceComponent);
						this.registeredComponents[key]=sourceDIComponent;					
					}
					else
					{
						this.registerComponent(key,sourceComponent);
					}
				}
				else
				{
					throw new Error("Wrong key : "+key+" DIComponent key must be uniqe");
				}
			}
			
			DILocator.initializeContainer(this);
			
			var app:Application = Application(Application.application)
			//Auto inject for annotated visual components
			app.addEventListener(Event.ADDED,onVisComponentAdded,false, 0 ,true);
//			app.systemManager.addEventListener(Event.ADDED,onVisComponentAdded,false, 0 ,true);
//			app.addEventListener(Event.REMOVED,onVisComponentRemoved,false, 0 ,true);
//			app.systemManager.addEventListener(Event.REMOVED,onVisComponentRemoved,false, 0 ,true);
			
			
			this.dispatchEvent(new InjectionContainerEvent(InjectionContainerEvent.INJECTION_INIT_COMPLETE));
			
		}
		
		/**
		 * Finds component by its key. New instance is created if necesarry. 
		 * @param key unique name of registered components
		 * @param context context name; used only with <i>context</i> scope
		 * @return found component.
		 *  
		 */
		public function getComponentById(key:String, context:String = null):Object 
		{
			var diComp:IDIComponent = this.registeredComponents[key] as IDIComponent;
			if (diComp) 
			{
				return diComp.getInstance(context);
			}
			else
			{
				throw new Error("Component with specified key not found. key : "+key);
			}
		}
		
		/**
		 * Finds component implementing specified interface.. Returns first found component that implements given interface. 
		 * @param interfaceName full qualified interface name
		 * @param context context name; used only with <i>context</i> scope
		 * @return found component
		 * 
		 */
		public function getComponentByInterface(interfaceName:String, context:String = null):Object
		{
			var searchedInterface:Class = getDefinitionByName(interfaceName) as Class;
			var foundItem:Object = null;
			if (searchedInterface != null)
			{
				for (var i:String in this.registeredComponents)
				{
					var tt:Class = IDIComponent(this.registeredComponents[i]).componentClass;
					if ((searchedInterface == tt) ||
						(extendsOrImplements(tt,searchedInterface)))
					{
						foundItem = this.getComponentById(i,context);
						break;
					} 
					
				}	
			}
			else
			{
				throw new Error("Class or interface not found : "+interfaceName);
			}
			 
			return foundItem;
			 
		}
		
		/**
		 * Removes component from memory 
		 * @param componentId
		 * @param context
		 * 
		 */
		public function removeComponentById(componentId:String, context:String):void
		{
			var diComp:IDIComponent = this.registeredComponents[componentId] as IDIComponent;
			if (diComp) 
			{
				return diComp.removeInstance(context);
			}
			else
			{
				throw new Error("Component with specified key not found. key : "+componentId);
			}
		}
		
		/**
		 *  Removes component from memory 
		 * @param interfaceName
		 * @param context
		 * 
		 */
		public function removeComponentByInterface(interfaceName:String, context:String):void
		{
			var searchedInterface:Class = getDefinitionByName(interfaceName) as Class;
			var foundItem:Object = null;
			if (searchedInterface != null)
			{
				for (var i:String in this.registeredComponents)
				{
					var tt:Class = IDIComponent(this.registeredComponents[i]).componentClass;
					if ((searchedInterface == tt) ||
						(extendsOrImplements(tt,searchedInterface)))
					{
						this.removeComponentById(i,context);
					} 
					
				}	
			}
			else
			{
				throw new Error("Class or interface not found : "+interfaceName);
			}
			 
		}
		
		
		private function extendsOrImplements(theClass:Class, base:Class):Boolean
		{
			var descr:XML = describeType(theClass);
			var implementations:XMLList = descr.factory.children().(name() == "extendsClass" || name() == "implementsInterface");
			var foundLen:int = implementations.(@type == getQualifiedClassName(base)).length();
			var result:Boolean = (foundLen > 0);
			return result;	
		} 
		
		
		private function onVisComponentAdded(event:Event):void
		{
			var cName:String = getQualifiedClassName(event.target);
			if (cName.substring(0, 3) == 'mx.' 
				|| cName.substring(0, 6) == 'flash.' 
				|| cName.substring(0, 4) == 'air.' 
				|| cName.substring(0, 3) == 'fl.')
			{
				return;
			}	
			
			if (cName == "pages::FeaturesPage")
			{
				var descr:XML = describeType(event.target); 
				var autoinjectMeta:XMLList = descr.children().(name() == "metadata" && @name == "Autoinject");
				if (autoinjectMeta.length() > 0)
				{
					MetaInjecter.configure(event.target);
				}
			}
		}
		

		
				
		
	}
}